package com.nenu.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.cluster.ClusterState;
import org.apache.ignite.configuration.*;
import org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi;
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ignite配置类
 */
@Slf4j
@Configuration
public class IgniteConfig {

    private IgniteCache<Object, Object> cache;

    @Bean
    public Ignite igniteInstance() {

        // 创建Ignite实例
        Ignite ignite = Ignition.start(getIgniteConfiguration());
        //激活集群
        ignite.cluster().state(ClusterState.ACTIVE);
        //启用自动调整功能，使得Ignite在检测到集群节点集变化时自动更新基线拓扑。
        ignite.cluster().baselineAutoAdjustEnabled(true);
        //启用自动调整功能后，还可设置自动调整的超时时间，该时间决定了Ignite在节点集变化后等待多久来确定是否更新基线拓扑。如果在这段时间内节点集没有进一步变化，Ignite将更新基线拓扑以匹配当前的节点集
        ignite.cluster().baselineAutoAdjustTimeout(300000);//ms
        log.info("igniteInstance完毕");

        // 创建redis缓存
        cache = ignite.getOrCreateCache(getRedisCacheConfiguration());

        //创建数据库缓存及schema
        CacheConfiguration<Object, Object> cacheCfg = new CacheConfiguration<>(getDBCacheConfiguration("MODEL"));
        cacheCfg.setSqlSchema("MODEL");
        if (!ignite.cacheNames().contains("MODEL")) {
            ignite.createCache(cacheCfg);
        }
        CacheConfiguration<Object, Object> cacheCfg2 = new CacheConfiguration<>(getDBCacheConfiguration("BUILDER"));
        cacheCfg2.setSqlSchema("BUILDER");
        if (!ignite.cacheNames().contains("BUILDER")) {
            ignite.createCache(cacheCfg2);
        }

        return ignite;
    }

    /**
     * 获取Ignite配置
     */
    private IgniteConfiguration getIgniteConfiguration() {
        // 创建Ignite配置实例
        IgniteConfiguration cfg = new IgniteConfiguration();

        // 配置Ignite实例的名称
        cfg.setIgniteInstanceName("engineInstance");

        // 设置服务器故障检测超时时间，单位ms,该配置决定了系统检测到服务器故障并停止向其发送请求所需的时间
        cfg.setFailureDetectionTimeout(30000);
        // 设置客户端故障检测超时时间，单位ms,客户端故障检测用于监控与服务端的连接状态，此超时时间决定了检测频率
        cfg.setClientFailureDetectionTimeout(50000);

        // 创建一个TcpCommunicationSpi实例
        TcpCommunicationSpi tcpSpi = new TcpCommunicationSpi();
        //设置消息队列的限制，以控制并发处理的消息数量
        tcpSpi.setMessageQueueLimit(100);
        cfg.setCommunicationSpi(tcpSpi);

        TcpDiscoverySpi tcpDiscoverySpi = new TcpDiscoverySpi();
        // 设置 SPI 的超时时间。
        tcpDiscoverySpi.setSocketTimeout(5000); // 5 seconds
        tcpDiscoverySpi.setAckTimeout(5000); // 5 seconds
        tcpDiscoverySpi.setJoinTimeout(5000);
        cfg.setDiscoverySpi(tcpDiscoverySpi);

        // 配置Ignite缓存的持久化策略
        cfg.isPersistentStoreEnabled();

        // 设置为服务器节点
        //cfg.setClientMode(false);

        // 客户端配置下面
        /*cfg.setClientMode(true);
        TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
        ipFinder.setAddresses(Collections.singletonList("127.0.0.1:47500..47509"));
        cfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(ipFinder));*/

        // 设置数据存储配置
        DataStorageConfiguration dsCfg = new DataStorageConfiguration()
            .setDefaultDataRegionConfiguration(getDataRegionConfiguration());
        //WAL 段的大小设置为了 512MB。然而，如果你不设置 setWalSegmentSize，Ignite 将继续使用默认的 256MB 大小。这个默认值是在 Ignite 的内部配置中设定的
        //dsCfg.setWalSegmentSize(512 * 1024 * 1024);
        //设置WAL模式，FSYNC, LOG_ONLY, BACKGROUND, 和 NONE，默认是FSYNC,这意味着每次事务提交或数据更改时，相关的日志记录都会被同步到磁盘，以确保数据的持久性和一致性
        dsCfg.setWalMode(WALMode.FSYNC);
        cfg.setDataStorageConfiguration(dsCfg);

        //设置ignite数据存储区域
        cfg.setWorkDirectory("D:\\server\\igniteWorkDemoDir");

        /**
         * metricsLogFrequency：单位ms，它控制着这些指标被记录到日志中的频率。默认情况下，Ignite每隔一段时间（默认是30秒）
         * 就会记录一次这些指标。如果你发现这些日志输出过于频繁或者不再需要它们，你可以通过将 metricsLogFrequency 设置为0来禁用这些日志输出
         */
        cfg.setMetricsLogFrequency(20000);

        /**
         * 在Apache Ignite中，cfg.setPeerClassLoadingEnabled(true); 这行代码的作用是启用Peer Class Loading特性。
         * Peer Class Loading（PCL）是Ignite的一个重要特性，它允许一个节点加载并分发类到集群中的其他节点。当启用PCL时，如果一个节点需要一个类，但它没有在类路径中找到，那么它会请求集群中其他已经加载了该类的节点将类发送过来。这可以避免在所有节点上手动部署相同的类库，简化了开发和运维工作。
         * 但是，PCL也有一些潜在的风险，比如可能会引入不兼容的类版本，或者恶意代码。因此，在生产环境中使用PCL时，需要确保集群内的所有节点都是可信的，并且有适当的控制机制来防止潜在的安全问题
         */
        cfg.setPeerClassLoadingEnabled(false);

        return cfg;
    }

    /**
     * 内存设置
     * @return
     */
    private DataRegionConfiguration getDataRegionConfiguration() {
        return new DataRegionConfiguration()
                //数据区域名称
                .setName("engine")
                //设置内存大小限制，超出此限制的数据将溢出到磁盘,当达到最大大小时，Ignite可能会根据内部淘汰策略（如LRU）来移除旧数据
                .setInitialSize(1024 * 1024 * 1024L) // 设置初始内存1GB,单位byte
                .setMaxSize(1024 * 1024 * 1024L) // 设置最大内存1GB,单位byte
                //设置是否持久化到磁盘（可选）,如果启用，当Ignite关闭时，内存中的数据将被写入磁盘, 再次启动时，这些数据将从磁盘加载回内存
                .setPersistenceEnabled(true);
    }

    /**
     * 数据库缓存配置
     * @return
     */
    private CacheConfiguration<Object, Object> getDBCacheConfiguration(String cacheName) {
        return new CacheConfiguration<>(cacheName)
                // 设置数据区域名称,指定缓存数据将被存储在该命名的数据区域中。
                //.setDataRegionName("engine")
                // 设置缓存模式为分区模式。在这种模式下，数据被分割成多个分区，每个分区可以被存储在不同的节点上。
                .setCacheMode(CacheMode.PARTITIONED)
                // 设置原子性模式，如ATOMIC（原子性）或TRANSACTIONAL（事务性）
                //.setAtomicityMode(CacheAtomicityMode.ATOMIC)
                // 设置缓存备份的数量为1。这意味着除了主要节点外，还会在另一个节点上存储缓存数据的副本，以提高数据的可靠性。
                .setBackups(1)
                // 设置写入同步模式为仅主节点同步。这意味着写入操作只会在缓存的主节点上同步，以提高写入性能。
                .setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
    }

    /**
     * redis缓存配置
     * @return
     */
    private CacheConfiguration<Object, Object>
    getRedisCacheConfiguration() {
        return new CacheConfiguration<>("redisCache")
                // 设置数据区域名称,指定缓存数据将被存储在该命名的数据区域中。
                //.setDataRegionName("engine")
                // 设置缓存模式为分区模式。在这种模式下，数据被分割成多个分区，每个分区可以被存储在不同的节点上。
                .setCacheMode(CacheMode.PARTITIONED)
                // 设置原子性模式，如ATOMIC（原子性）或TRANSACTIONAL（事务性）
                //.setAtomicityMode(CacheAtomicityMode.ATOMIC)
                // 设置缓存备份的数量为1。这意味着除了主要节点外，还会在另一个节点上存储缓存数据的副本，以提高数据的可靠性。
                .setBackups(1)
                // 设置写入同步模式为仅主节点同步。这意味着写入操作只会在缓存的主节点上同步，以提高写入性能。
                .setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
    }


}
